/*
 * @(#)AbstractDataHandler.java $version 2012-3-2
 * 
 * Copyright 2011 RogueDevelopment Gruop. All rights Reserved.
 * ROGUEDEVELOPMENT PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nadostar.extensions.data;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nadostar.extensions.util.FileResource;
import com.nadostar.extensions.util.Reloadable;
import com.nadostar.extensions.util.Resource;

/**
 * {@link DataHandler}의 타입 변환 API를 구현하고 있는 추상 클래스.
 * <p/>
 * 타입 변환 API는 해당 타입으로 변환할 노드의 키 값을 인수로 받으며, 해당 노드가 존재하지 않거나 타입 변환에 실패한 경우에 발생하는
 * Exception 대신 반환되는 default 값을 추가적으로 지정할 수 있다.
 * <p/>
 * 숫자 타입으로의 변환은 아래와 같은 형태의 API 호출을 통해 이루어진다.
 * 
 * @author LEESUNG
 * 
 */
public abstract class AbstractDataHandler implements DataHandler, Reloadable {
	protected static final Pattern PATTERN = Pattern
			.compile("\\$\\{[^\\}]*\\}"); // share

	protected Log log = LogFactory.getLog(DataHandler.class);

	protected String name;
	protected String location;

	protected Resource resource;

	/**
	 * Instantiates a new abstract data handler.
	 */
	public AbstractDataHandler() {
	}

	/**
	 * Instantiates a new abstract data handler.
	 * 
	 * @param location
	 *            the location
	 */
	public AbstractDataHandler(String location) {
		this.location = location;
	}

	/**
	 * 설정된 위치 {@code location}의 XML 파일을 읽는다.
	 */
	protected abstract void initialize();

	public String getHandlerName() {
		return name;
	}

	public String getLocation() {
		return location;
	}

	public DataHandler subConfig(String key) {
		return peek(key);
	}

	public List<DataHandler> sublists(String path) {
		return peekAll(path);
	}

	public List<DataHandler> childConfigs() throws DataHandlerException {
		return selectChildren();
	}

	public String get(String key) throws DataHandlerException {
		String value = evaluate(key, true);

		if (value == null) {
			throw new DataHandlerException(key + " is not found in "
					+ identity());
		}

		return value;
	}

	public String getRaw(String key) throws DataHandlerException {
		String value = evaluate(key, false);

		if (value == null) {
			throw new DataHandlerException(key + " is not found in "
					+ identity());
		}

		return value;
	}

	public DataHandler subconfig(String key) throws DataHandlerException {
		return select(key);
	}

	public List<DataHandler> subConfigs(String key) throws DataHandlerException {
		return selectAll(key);
	}

	public List<DataHandler> selectChildren() throws DataHandlerException {
		List<DataHandler> children = peekChildren();

		if (children.isEmpty()) {
			throw new DataHandlerException(getRootName()
					+ " don't have children");
		}

		return children;
	}

	/**
	 * Sets the handler name.
	 * 
	 * @param name
	 *            the new handler name
	 */
	public void setHandlerName(String name) {
		this.name = name;
	}

	/**
	 * Sets the location.
	 * 
	 * @param location
	 *            the new location
	 */
	public void setLocation(String location) {
		this.location = location;
	}

	/**
	 * Resolve.
	 * 
	 * @param result
	 *            the result
	 * 
	 * @return the string
	 * 
	 * @throws DataHandlerException
	 *             the data handler exception
	 */
	protected String resolve(String result) throws DataHandlerException {
		if (result == null || result.indexOf('$') < 0) {
			return result; // fast cut
		}

		Matcher matcher = PATTERN.matcher(result);

		StringBuffer builder = new StringBuffer();

		while (matcher.find()) {
			String matched = matcher.group();

			if (matched == null) {
				throw new DataHandlerException();
			}

			String reference = matched.substring(2, matched.length() - 1); // ${...}

			String referent = get(reference);
			matcher.appendReplacement(builder, referent);
		}

		matcher.appendTail(builder);
		return builder.toString();
	}

	/**
	 * @see java.util.Map#get(java.lang.Object) {@inheritDoc}
	 */
	public Object get(Object key) {
		return get((String) key);
	}

	public String get(String key, String defaultValue) {
		String value = evaluate(key, true);

		if (value == null) {
			return defaultValue;
		}

		return value;
	}

	public boolean getBoolean(String key) {
		return getBoolean(key, false);
	}

	public boolean getBoolean(String key, boolean defaultValue) {
		String value = evaluate(key, true);

		if (value == null) {
			return defaultValue;
		}

		if ("true".equalsIgnoreCase(value)) {
			return true;
		} else if ("false".equalsIgnoreCase(value)) {
			return false;
		}

		return defaultValue;
	}

	public int getInt(String key) {
		return Integer.parseInt(get(key));
	}

	public int getInt(String key, int defaultValue) {
		String value = evaluate(key, true);

		if (value != null) {
			return Integer.parseInt(value);
		}

		return defaultValue;
	}

	public double getDouble(String key) {
		return Double.parseDouble(get(key));
	}

	public double getDouble(String key, double defaultValue) {
		String value = evaluate(key, true);

		if (value != null) {
			return Double.parseDouble(value);
		}

		return defaultValue;
	}

	public long getLong(String key) {
		return Long.valueOf(get(key));
	}

	public long getLong(String key, long defaultValue) {
		String value = evaluate(key, true);

		if (value != null) {
			return Long.parseLong(value);
		}

		return defaultValue;
	}

	public float getFloat(String key) {
		return Float.parseFloat(get(key));
	}

	public float getFloat(String key, float defaultValue) {
		String value = evaluate(key, true);

		if (value != null) {
			return Float.parseFloat(value);
		}

		return defaultValue;
	}

	public boolean isChanged() {
		if (resource instanceof FileResource) {
			return ((FileResource) resource).isModified();
		}

		return false;
	}

	public boolean load() {
		destroy();
		initialize();
		return true;
	}

	/**
	 * Identity.
	 * 
	 * @return the string
	 */
	public String identity() {
		return (name == null ? "" : name) + location == null ? ""
				: (name == null ? location : ":" + location);
	}

	/**
	 * @see java.util.Map#size() {@inheritDoc}
	 */
	public int size() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#clear() {@inheritDoc}
	 */
	public void clear() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#isEmpty() {@inheritDoc}
	 */
	public boolean isEmpty() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#containsKey(java.lang.Object) {@inheritDoc}
	 */
	public boolean containsKey(Object key) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#containsValue(java.lang.Object) {@inheritDoc}
	 */
	public boolean containsValue(Object value) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#values() {@inheritDoc}
	 */
	public Collection values() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#putAll(java.util.Map) {@inheritDoc}
	 */
	public void putAll(Map tt) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#entrySet() {@inheritDoc}
	 */
	public Set entrySet() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#keySet() {@inheritDoc}
	 */
	public Set keySet() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#remove(java.lang.Object) {@inheritDoc}
	 */
	public Object remove(Object key) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#put(java.lang.Object, java.lang.Object) {@inheritDoc}
	 */
	public Object put(Object key, Object value) {
		throw new UnsupportedOperationException();
	}
}
